package com.blog.cloud.gateway.config;

import com.blog.cloud.gateway.filter.JwtAuthenticationFilter;
import com.blog.cloud.gateway.handler.GlobalServerAccessDeniedHandler;
import com.blog.cloud.gateway.handler.GlobalServerAuthenticationEntryPoint;
import com.blog.cloud.gateway.properties.AuthIgnoreProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.LinkedList;

import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;

@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final AuthIgnoreProperties properties;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final GlobalServerAccessDeniedHandler globalServerAccessDeniedHandler;
    private final GlobalServerAuthenticationEntryPoint globalServerAuthenticationEntryPoint;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // 访问白名单
        String[] whiteList = properties.getIgnoreUrls().toArray(String[]::new);
        System.out.println(Arrays.toString(whiteList));
        http
                .csrf().disable()
                .authorizeExchange((authorize) -> authorize
                        .pathMatchers(HttpMethod.OPTIONS).permitAll()
                        .pathMatchers(whiteList).permitAll()
                        .pathMatchers("/admin/**").hasRole("ADMIN")
                        .pathMatchers("/db/**").access((authentication, context) -> hasRole("ADMIN").check(authentication, context).filter(decision -> !decision.isGranted()).switchIfEmpty(hasRole("DBA").check(authentication, context)))
                        .anyExchange().authenticated())
                .authenticationManager(reactiveAuthenticationManager())
                .addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                .exceptionHandling()
                .accessDeniedHandler(globalServerAccessDeniedHandler)
                .authenticationEntryPoint(globalServerAuthenticationEntryPoint);
        return http.build();
    }

    /**
     * BCrypt密码编码
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * 注册用户信息验证管理器，可按需求添加多个按顺序执行
     */
    @Bean
    public ReactiveAuthenticationManager reactiveAuthenticationManager() {
        LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
        managers.add(authentication -> {
            // 其他登陆方式 (比如手机号验证码登陆) 可在此设置不得抛出异常或者 Mono.error
            return Mono.empty();
        });
        // 必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此 AuthenticationManager 会调用 Mono.error 造成后面的 AuthenticationManager 不生效
        return new DelegatingReactiveAuthenticationManager(managers);
    }

}
